#include <stdio.h>
#include <CL/cl.h>
#include "aux.h"

int main()
{
    // Platform & Device Query
    cl_uint num_platforms;
    if (clGetPlatformIDs(0, NULL, &num_platforms)!=CL_SUCCESS){
        printf("Error!\n");
    } else {
        printf("Number of avaliable platforms: %u\n", num_platforms);
    }

    cl_platform_id platform;
    clGetPlatformIDs(1, &platform, NULL);
    printf("...First platform chosen\n");

    cl_uint num_devices;
    if (clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 0,
                       NULL, &num_devices)!=CL_SUCCESS){
        printf("Error!\n");
    } else {
        printf("Number of avaliable devices: %u\n", num_devices);
    }

    cl_device_id device;
    clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    printf("...First device chosen\n");

    // Context Creation
    cl_context context;
    cl_int err;
    context = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
    if (err==CL_SUCCESS){
        printf("Context created\n");
    } else {
        printf("Error creating context\n");
    }

    // Program Objects:
    const char* programSource = readKernel();
    cl_program program;
    program = clCreateProgramWithSource(context, 1,
                                        (const char**)&programSource,
                                        NULL, &err);
    if (err==CL_SUCCESS){
        printf("Program created\n");
    } else {
        printf("Error creating program\n");
    }

    err = clBuildProgram(program, 1, &device, NULL, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Program compiled\n");
    } else {
        printf("Error compiling program\n");
    }

    cl_kernel kernel;
    kernel = clCreateKernel(program, "vecadd", &err);
    if (err==CL_SUCCESS){
        printf("Kernel created\n");
    } else {
        printf("Error creating kernel\n");
    }

    // Host Data
    int a_data[LENGTH], b_data[LENGTH], c_res[LENGTH];
    for (int i=0; i<LENGTH; i++){
        a_data[i] = (int)((100.0*rand())/RAND_MAX);
        b_data[i] = (int)((100.0*rand())/RAND_MAX);
        printf("A: %d  B: %d\n", a_data[i], b_data[i]);
    }

    // Memory Objects:
    cl_mem a_in, b_in, c_out;

    a_in = clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(int)*LENGTH, NULL,
                          &err);
    if (err==CL_SUCCESS){
        printf("Buffer A created\n");
    } else {
        printf("Error creating buffer A\n");
    }

    b_in = clCreateBuffer(context, CL_MEM_READ_ONLY, sizeof(int)*LENGTH, NULL,
                          &err);
    if (err==CL_SUCCESS){
        printf("Buffer B created\n");
    } else {
        printf("Error creating buffer B\n");
    }

    c_out = clCreateBuffer(context, CL_MEM_WRITE_ONLY, sizeof(int)*LENGTH, NULL,
                           &err);
    if (err==CL_SUCCESS){
        printf("Buffer C created\n");
    } else {
        printf("Error creating buffer C\n");
    }

    // Set Kernel Arguments
    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &a_in);
    err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &b_in);
    err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &c_out);
    if (err==CL_SUCCESS){
        printf("Kernel's arguments configured\n");
    } else {
        printf("Error configuring the kernel's arguments\n");
    }

    // Create Command Queue
    cl_command_queue commands;
    commands = clCreateCommandQueue(context, device, 0, &err);
    if (err==CL_SUCCESS){
        printf("Command queue created\n");
    } else {
        printf("Error creating command queue\n");
    }

    // Memory Write
    err = clEnqueueWriteBuffer(commands, a_in, CL_TRUE, 0, sizeof(int)*LENGTH,
                               a_data, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer A copied to device\n");
    } else {
        printf("Error copying buffer A to device\n");
    }

    err = clEnqueueWriteBuffer(commands, b_in, CL_TRUE, 0, sizeof(int)*LENGTH,
                               b_data, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer B copied to device\n");
    } else {
        printf("Error copying buffer B to device\n");
    }

    // Kernel Execution
    size_t global = LENGTH;
    size_t local = LENGTH; // tamaño del workgroup
    err = clEnqueueNDRangeKernel(commands, kernel, 1, NULL, &global, &local, 0,
                                 NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Kernel sent to device\n");
    } else {
        printf("Error sending kernel to device\n");
    }

    clFinish(commands);

    // Memory Read
    err = clEnqueueReadBuffer(commands, c_out, CL_TRUE, 0, sizeof(int)*LENGTH,
                              c_res, 0, NULL, NULL);
    if (err==CL_SUCCESS){
        printf("Buffer C copied from device\n");
    } else {
        printf("Error copying buffer C from device\n");
    }

    for (int i=0; i<LENGTH; i++){
        printf("%d + %d = %d\n", a_data[i], b_data[i], c_res[i]);
    }
}
